//!
/************************************************************************************************************
 * @file RBApplication.cpp
 ************************************************************************************************************/

/************************************************************************************************************
 * Precompiled Header
 ************************************************************************************************************/
#include "RBApplicationPCH.h" 

/************************************************************************************************************
 * Includes
 ************************************************************************************************************/

#include "RBApplication.h"

#include <RBMain/RBEntity.h>
#include <RBTransformSystem/RBTransformComponent.h>

#include <RBMain/RBDAEReader.h>
#include <RBMain/RBDefaultStream.h>
#include <RBMain/RBLight.h>
#include <RBMain/RBProfiler.h>
#include <RBMain/RBRBSReader.h>
#include <RBMain/RBRenderPassDebug.h>
#include <RBMain/RBResourceManager.h>
#include <RBMain/RBSystem.h>
#include <RBMain/RBVisibilityManager.h>
#include <RBMain/RBVisibilityComponent.h>
#include <RBMain/RBRenderPass.h>

#ifdef _WIN32
#include <RBD3D11Renderer/RBD3D11Renderer.h>
#include <RBD3D11Renderer/sdkmesh_reader.h>
#endif

#include <RBRenderProcess/RBEffectFactory.h>

#include <Windows.h>

#include <Shiny.h>
#include <stb.h>

/************************************************************************************************************
 * Defines
 ************************************************************************************************************/

/************************************************************************************************************
 * Implementation
 ************************************************************************************************************/

/************************************************************************************************************
 * Constructor
 ************************************************************************************************************/
RBApplication::RBApplication()
: Width   (0)
, Height  (0)
, m_renderer      (NULL)
, m_fStepTime       (0)
, m_fCurrentTime    (0)
, m_dt              ((uint32)0)
, m_FBWindow        (0,0,0,0)
, m_previousMousePos(0,0)
, m_main_scene      (NULL)
, m_camera_speed    (1.0f)
{
    new RBInput();
    new RBProfilerManager();
    new RBResourceManager();

    RBResourceManager::GetSingleton()->AddPath("./Data");
    RBResourceManager::GetSingleton()->AddPath("./Data/Images");
    RBStream::RegisterReader(new RBDAEReader());
    RBStream::RegisterReader(new RBRBSReader());
    RBStream::RegisterReader(new sdkmesh_reader());

    RBComponent::RegisterComponent<RBBVolume>();
    RBComponent::RegisterComponent<RBCamera>();
    RBComponent::RegisterComponent<RBTransformComponent>();
    RBComponent::RegisterComponent<RBGeometry>();
    RBComponent::RegisterComponent<RBLight>();
//    RBComponent::RegisterComponent<RBVisibilityComponent>();

    m_pHUDScene = new RBVisibilityManager;
    m_pHUDScene->IncRefCount();

	stb_work_numthreads(stb_processor_count()-1);

}

/************************************************************************************************************
 * Destructor
 ************************************************************************************************************/
RBApplication::~RBApplication()
{
    RBComponent::UnregisterComponent<RBLight>();
    RBComponent::UnregisterComponent<RBGeometry>();
    RBComponent::UnregisterComponent<RBTransformComponent>();
    RBComponent::UnregisterComponent<RBCamera>();
    RBComponent::UnregisterComponent<RBBVolume>();

    m_pHUDScene->DecRefCount();
    RBStream::ClearReaders();
    delete RBResourceManager::GetSingleton();
    delete RBProfilerManager::GetSingleton();
    delete RBInput::GetSingleton();
    if (m_renderer)
    {
        m_renderer->Release();
        m_renderer = 0;
    }
}

/***********************************************************************************************************
 * Application Initialization
 ***********************************************************************************************************/
bool RBApplication::Init(RBRenderer* renderer)
{
	m_renderer = renderer;

    RBEffectFactory::GetSingleton()->AddDirectory("./Data");
    RBEffectFactory::GetSingleton()->Parse();

    RBRenderPassDebug::Init(m_renderer);

    InitCameraNode();

    m_main_scene = CreateVisibilityMgr();
    if (m_main_scene)
    {
        m_main_scene->IncRefCount();
    }

    m_kTimer.Reset();
    MeasureTime();

    return true;
}

/***********************************************************************************************************
 * Release
 ***********************************************************************************************************/
void RBApplication::Release()
{
    for (uint32 ui=0; ui<m_entities.size(); ++ui)
    {
        OnEntityUnloaded(m_entities[ui]);
    }
    m_entities.clear();

    if (m_main_scene)
    {
        m_main_scene->DecRefCount();
    }
    m_spCameraOrbit = 0;
    RBRenderPassDebug::Release();
}

/***********************************************************************************************************
 * Main Loop
 ***********************************************************************************************************/
void RBApplication::OnIdle()
{
    m_dt = MeasureTime();

    m_fStepTime += m_dt.s();

    UpdateMouse();

    Dt dt((uint32)10);

    UpdateCamera(dt);

    Render(m_dt);

    RBInput::GetSingleton()->Update();

}
/***********************************************************************************************************
 * Creates the scene to display
 ***********************************************************************************************************/
void RBApplication::InitCameraNode()
{
    // Create a default Camera with a camera node

    m_spCameraOrbit = RBEntity::CreateEntity();
    RBEntity& cameraOrbit = RBEntity::Get(m_spCameraOrbit);

    RBTransformComponentHandle pXFormOrbit =  RBTransformComponent::Create(m_spCameraOrbit);
    RBTransformComponentHandle pXFormCamera = RBTransformComponent::Create(m_spCameraOrbit);
    RBTransformComponent::Get(pXFormOrbit).AddChild(pXFormCamera);

    m_camera = RBCamera::Create(m_spCameraOrbit, pXFormCamera);
    cameraOrbit.AddComponent<RBCamera>(m_camera);
    m_partCamera = cameraOrbit.AddComponent<RBTransformComponent>(pXFormCamera);
    m_partOrbit = cameraOrbit.AddComponent<RBTransformComponent>(pXFormOrbit);


}

/***********************************************************************************************************
 * Retrieve the time elapsed since last call
 ***********************************************************************************************************/
Dt RBApplication::MeasureTime()
{
    float32 fNewTime = m_kTimer.GetTime();
    Dt  dt(m_kTimer.GetTime() - m_fCurrentTime);
    if (dt.ms() < 5)
    {
        //RBSystem::Sleep(5-dt.ms());
        fNewTime = m_kTimer.GetTime();
        dt = Dt(fNewTime - m_fCurrentTime);
    }

    m_fCurrentTime = fNewTime;
    return dt;
}

/***********************************************************************************************************
 *
 ***********************************************************************************************************/
void RBApplication::UpdateCamera(const Dt& dt)
{
    float32 fAspectRatio = static_cast<float32>(Width) / Height;
    m_camera->SetViewFrustum(RBMath::RB_PI * 0.5f, fAspectRatio, 0.1f, 1000.0f, false);

    int2 delta = RBInput::GetSingleton()->GetMouseDelta();
    float32 fMoveSpeed = 0.03f * m_camera_speed;

    RBHandle<RBTransformComponent> pXFormOrbit = RBEntity::GetComponent<RBTransformComponent>(m_spCameraOrbit, m_partOrbit);
    RBHandle<RBTransformComponent> pXFormCamera = RBEntity::GetComponent<RBTransformComponent>(m_spCameraOrbit, m_partCamera);

    if (RBInput::GetSingleton()->IsDown(RBInput::IK_LBUTTON))
    {
        float32 fMouseDelta = 0.45f * (float32)dt.s();
        float32 fXRot = delta.y * fMouseDelta;
        float32 fYRot = delta.x * fMouseDelta;
        float3x3 xRot; xRot.SetXRotation(fXRot);
        float3x3 yRot; yRot.SetYRotation(fYRot);

        pXFormOrbit->LocalXForm().Rotate = yRot * pXFormOrbit->LocalXForm().Rotate * xRot;
    }

    if (RBInput::GetSingleton()->IsDown(RBInput::IK_MBUTTON))
    {
        float32 fMouseDelta = 30 * (float32)dt.s();
        float3 vRight; pXFormCamera->WorldXForm().Rotate.GetCol(0,vRight);
        float3 vUp; pXFormCamera->WorldXForm().Rotate.GetCol(1,vUp);
        pXFormOrbit->LocalXForm().Translate -= delta.x * fMouseDelta * vRight;
        pXFormOrbit->LocalXForm().Translate += delta.y * fMouseDelta * vUp;
    }

    if (RBInput::GetSingleton()->IsDown(RBInput::IK_RBUTTON))
    {
        float32 fMouseDelta = 90 * (float32)dt.s();
        pXFormCamera->LocalXForm().Translate.z += delta.y * fMouseDelta;
    }

    if (RBInput::GetSingleton()->IsDown(RBInput::IK_W) || RBInput::GetSingleton()->IsDown(RBInput::IK_S))
    {
        float3 vBack; pXFormCamera->WorldXForm().Rotate.GetCol(2,vBack);
        float32 fSign = RBInput::GetSingleton()->IsDown(RBInput::IK_W) ? -1.0f : 1.0f;
        pXFormOrbit->LocalXForm().Translate += fMoveSpeed * fSign * vBack;
    }

    if (RBInput::GetSingleton()->IsDown(RBInput::IK_A) || RBInput::GetSingleton()->IsDown(RBInput::IK_D))
    {
        float3 vRight; pXFormCamera->WorldXForm().Rotate.GetCol(0,vRight);
        float32 fSign = RBInput::GetSingleton()->IsDown(RBInput::IK_A) ? -1.0f : 1.0f;
        pXFormOrbit->LocalXForm().Translate += fMoveSpeed * fSign * vRight;
    }


    pXFormCamera->LocalXForm().Translate.z = RBMath::Max(0.0f, pXFormCamera->LocalXForm().Translate.z);

    RBTransformComponent::Update(pXFormOrbit);

}

/***********************************************************************************************************
 *
 ***********************************************************************************************************/
void RBApplication::OnKey(RBInput::KeyType button, RBInput::KeyState state)
{
    RBInput::GetSingleton()->SetValue(button, state);
}

/***********************************************************************************************************
 *
 ***********************************************************************************************************/
void RBApplication::Render(const Dt&)
{
    if (RBInput::GetSingleton()->IsPressed(RBInput::IK_R))
    {
        m_renderer->RestoreLostDevice(true);
    }

}

/**************************************************************************************************************************************************************
 * 
 **************************************************************************************************************************************************************/
RBRenderer* RBApplication::CreateRenderer()
{
#ifdef RB_USE_GL
    bool bUseGL = true;
#endif

    RBRenderer* pRenderer = 0;

#ifdef RB_USE_GL
    if (bUseGL)
    {
        pRenderer = RBGLRenderer::Create(RBGLRendererInit(m_window_width, m_window_height, m_hWnd));
    }
    else
#endif
    {
#pragma TODO("reimplement renderer construction")
//         pRenderer = RBRenderer::Create<RBD3D11Renderer>(RBD3D11RendererInit(m_uiWindowWidth, m_uiWindowHeight, m_hWnd));
    }

    return pRenderer;
}

/***********************************************************************************************************
 *
 ***********************************************************************************************************/
RBRenderer* RBApplication::GetRenderer() const
{
    return m_renderer;
}

/***********************************************************************************************************
 * 
 ***********************************************************************************************************/
void RBApplication::UpdateMouse()
{
    int width = m_FBWindow.z - m_FBWindow.x;
    int height = m_FBWindow.w - m_FBWindow.y;
    int xOffset = m_FBWindow.x;
    int yOffset = m_FBWindow.y;
    int32 x,y;
    RBSystem::GetCursorPos(x, y);

    int2 delta(x - m_previousMousePos.x, y-m_previousMousePos.y);

    bool bTileMouse = false;

    if (bTileMouse)
    {
        // Allows application to make mouse tile on the window
        if (RBInput::GetSingleton()->IsDown(RBInput::IK_LBUTTON) ||
            RBInput::GetSingleton()->IsDown(RBInput::IK_MBUTTON) ||
            RBInput::GetSingleton()->IsDown(RBInput::IK_RBUTTON))
        {
            int32 iOffset = 3;

            int32 xMin = m_FBWindow.x+iOffset;
            int32 xMax = m_FBWindow.z-iOffset;
            int32 yMin = m_FBWindow.y+iOffset;
            int32 yMax = m_FBWindow.w-iOffset;

            bool bMove = (x < xMin) || (x > xMax) || (y < yMin) || (y  > yMax);

            if (x < xMin)
                x = xMax;
            if (x > xMax)
                x = xMin;
            if (y < yMin)
                y = yMax;
            if (y  > yMax)
                y = yMin;

            if (bMove)
            {
                RBSystem::SetCursorPos(x,y);
            }
        }
    }

    m_previousMousePos.x = x;
    m_previousMousePos.y = y;

}

/***********************************************************************************************************
 * 
 ***********************************************************************************************************/
bool RBApplication::Open( const RBString& strFilename )
{
    RBStream* pStream = RBStream::Load(strFilename);
    if (!pStream)
    {
        return false;
    }

    m_entities.push_back(pStream->GetObjectAt(0));

    for (uint32 ui=0; ui<m_entities.size(); ++ui)
    {
        RBTransformComponent::UpdateEntity(m_entities[ui]);
        RBBVolume::UpdateEntity(m_entities[ui]);
    }

    for (uint32 ui=0; ui<m_entities.size(); ++ui)
    {
        OnEntityLoaded(m_entities[ui]);
    }

    delete pStream;
    return true;
}

/***********************************************************************************************************
 * 
 ***********************************************************************************************************/
void RBApplication::Close()
{
    for (uint32 ui=0; ui<m_entities.size(); ++ui)
    {
        OnEntityUnloaded(m_entities[ui]);
    }

    m_entities.clear();
}

/***********************************************************************************************************
 * 
 ***********************************************************************************************************/
bool RBApplication::Save( const RBString& strFilename )
{
    RBAssert(false, "Save not supported");

//     RBDefaultStream kStream;
// 
//     kStream.Open(strFilename, RBFile::FOM_WRITE);
//     kStream.Insert(m_spSceneRoot);
//     kStream.Serialize(RBStream::SM_WRITE);
//     kStream.Close();
    return true;
}

/***********************************************************************************************************
 * 
 ***********************************************************************************************************/
void RBApplication::ChangeResolution( const int2& size )
{
    if (m_renderer)
    {
        m_renderer->ChangeResolution(size.x, size.y);
    }

    if (m_camera)
    {
        m_camera->ChangeAspectRatio(float32(size.x)/size.y);
    }
}

void RBApplication::SetMousePos( const int2& mousePos )
{
    RBInput::GetSingleton()->SetMousePos(int2(mousePos.x,mousePos.y));
}

/***********************************************************************************************************
 * 
 ***********************************************************************************************************/
void RBApplication::OnEntityLoaded( RBEntityHandle pScene )
{
    if (m_main_scene)
    {
        m_main_scene->AddEntity(pScene);
    }
}

/***********************************************************************************************************
 * 
 ***********************************************************************************************************/
void RBApplication::OnEntityUnloaded( RBEntityHandle pScene )
{
    if (m_main_scene)
    {
        m_main_scene->RemoveEntity(pScene);
    }
}

/***********************************************************************************************************
 * 
 ***********************************************************************************************************/
RBVisibilityManager* RBApplication::CreateVisibilityMgr()
{
    return new RBVisibilityManager;
}

void RBApplication::render_scene( RBVisibilityManager* scene, RBCamera* view, RBRenderer* renderer, RBRenderTargetGroup* render_target, RBStringID /*technique*/ )
{
	RBGeometryArray geoms;
    {
        PROFILE_BLOCK(RBApplication_Visibility);
        rb_render_pass::collect_geometries(&geoms, view, scene);
    }

    {
        PROFILE_BLOCK(RBApplication_Render);
	    rb_render_pass::open_render_target_group(renderer, render_target, RBRenderer::CF_ALL, RBColorA(0,1,1,1));
	    rb_render_pass::render_geometries(renderer, geoms, view);
    }
}

void RBApplication::dump_profiler()
{
    PROFILE_UPDATE();
    //PROFILE_OUTPUT(NULL);
    std::string profile_perf = PROFILE_GET_TREE_STRING();
    ::OutputDebugString(profile_perf.c_str());
}
